
class CanvasDraw {
    ratio = 1 //参数放大的倍数
    canvasContext //canvas 上下文
    family = ""
    constructor(canvasId, ratio = 1) {
        this.canvasDom = document.getElementById(canvasId)
        this.ratio = ratio
        if (this.canvasDom.getContext) {
            this.canvasContext = this.canvasDom.getContext("2d")
        } else {
            console.error("实例化失败，请检查canvas标签id，或浏览器不支持canvas")
        }
    }
    
    /**
     * 加载字体 字体加载完成后在绘制文字 字体才能生效 建议await
     * @param {*} fontUrl 字体资源链接
     * @param {*} fontName 字体名称
     * @returns 
     */
    loadFace(fontUrl, fontName) {
        let that = this
        return new Promise((resolve, reject) => {
            // 创建一个新的FontFace对象
            const fontFace = new FontFace(fontName, `url("${fontUrl}")`);
            // 加载字体
            fontFace
                .load()
                .then((font) => {
                    // 将字体添加到文档
                    document.fonts.add(font);
                    that.family = fontName
                    resolve()
                })
                .catch((error) => {
                    console.error("Font could not be loaded. Error:", error);
                    reject(error)
                });
        })
    }
    /**
     * 重置样式
     */
    resetDefault() {
        this.canvasContext.strokeStyle = "#cccccc";
        this.canvasContext.fillStyle = "#cccccc";
        this.canvasContext.lineWidth = 1;
        this.canvasContext.textAlign = "left"
    }
    /**
     * 转换倍率
     * @param {*} num 
     * @returns 
     */
    conversionRatio(num) {
        return Number((num * this.ratio).toFixed(0))
    }
    /**
     * 将字符串按照指定长度截取成数组
     * @param {*} str 字符串
     * @param {*} chunkSize 指定截取的长度
     */
    splitStringIntoChunks(str, chunkSize) {
        const numChunks = Math.ceil(str.length / chunkSize);
        const chunks = new Array(numChunks);
        for (let i = 0, offset = 0; i < numChunks; i++, offset += chunkSize) {
            chunks[i] = str.substr(offset, chunkSize);
        }
        return chunks;
    }
    /**
     * 将一维数组转为二维数组
     * @param {*} array 原始数组
     * @param {*} chunkSize 指定的长度
     */
    chunkArray(array, chunkSize) {
        let result = [];
        for (let i = 0; i < array.length; i += chunkSize) {
            result.push(array.slice(i, i + chunkSize));
        }
        return result;
    }
    /**
     * 绘制竖线
     * @param {*} strokeStyle 线条颜色
     * @param {*} x 绘制起始x坐标
     * @param {*} y 绘制起始y坐标
     * @param {*} height 线条高度
     */
    drawVerticalLine(strokeStyle, x, y, height, lineWidth = 1) {
        this.resetDefault()
        this.drawLine(strokeStyle, { x: x, y: y }, { x: x, y: y + height }, lineWidth)
    }
    /**
     * 绘制横线
     * @param {*} strokeStyle 线条颜色
     * @param {*} x 绘制起始x坐标
     * @param {*} y 绘制起始y坐标
     * @param {*} width 
     */
    drawHorizontalLine(strokeStyle, x, y, width, lineWidth = 1) {
        this.resetDefault()
        this.drawLine(strokeStyle, { x: x, y: y }, { x: x + width, y: y }, lineWidth)
    }
    /**
     * 绘制线段
     * @param {*} strokeStyle 线条颜色
     * @param {*} start {x:0,y:0} 绘制起始坐标
     * @param {*} end {x:10,y:10} 绘制结束坐标
     */
    drawLine(strokeStyle, start, end, lineWidth = 1) {
        this.resetDefault()
        this.canvasContext.strokeStyle = strokeStyle;
        // 起点
        this.canvasContext.moveTo(this.conversionRatio(start.x || 0), this.conversionRatio(start.y || 0));
        // 终点
        this.canvasContext.lineTo(this.conversionRatio(end.x), this.conversionRatio(end.y || 0));
        // 绘制线段
        this.canvasContext.lineWidth = lineWidth
        this.canvasContext.stroke();
    }
    /**
    * 绘制矩形
    * @param {*} fillStyle 填充颜色
    * @param {*} x 绘制起始x坐标
    * @param {*} y 绘制起始y坐标
    * @param {*} width 矩形的宽
    * @param {*} height 矩形的高
    */
    drawRect(fillStyle, x, y, width, height) {
        this.resetDefault()
        // 设置半透明颜色
        // var transparentColor = 'rgba(255, 0, 0, 0.5)'; // 红色，50%透明度
        this.canvasContext.fillStyle = fillStyle;
        this.canvasContext.fillRect(
            this.conversionRatio(x),
            this.conversionRatio(y),
            this.conversionRatio(width),
            this.conversionRatio(height),
        );
    }
    /**
     * 绘制矩形框
     * @param {*} strokeStyle 边框颜色
     * @param {*} x 绘制起始x坐标
     * @param {*} y 绘制起始y坐标
     * @param {*} width 矩形的宽
     * @param {*} height 矩形的高
     */
    drawRectBorder(strokeStyle, x, y, width, height) {
        this.resetDefault()
        this.canvasContext.strokeStyle = strokeStyle;
        this.canvasContext.strokeRect(
            this.conversionRatio(x),
            this.conversionRatio(y),
            this.conversionRatio(width),
            this.conversionRatio(height),
        );
    }
    /**
     * 创建圆角矩形路径
     * @param {*} x 绘制起始x坐标
     * @param {*} y 绘制起始y坐标
     * @param {*} width 矩形的宽
     * @param {*} height 矩形的高
     * @param {*} radius 圆角
     */
    drawRoundedRectPath(x, y, width, height, radius = 10) {
        if (radius <= 0) {
            console.error("圆角大小不能为0")
            return
        }
        this.canvasContext.beginPath();
        this.canvasContext.moveTo(
            this.conversionRatio(x + radius),
            this.conversionRatio(y)
        );
        this.canvasContext.lineTo(
            this.conversionRatio(x + width - radius),
            this.conversionRatio(y)
        );
        this.canvasContext.arcTo(this.conversionRatio(x + width),
            this.conversionRatio(y),
            this.conversionRatio(x + width),
            this.conversionRatio(y + radius),
            this.conversionRatio(radius)
        );
        this.canvasContext.lineTo(
            this.conversionRatio(x + width),
            this.conversionRatio(y + height - radius)
        );
        this.canvasContext.arcTo(
            this.conversionRatio(x + width),
            this.conversionRatio(y + height),
            this.conversionRatio(x + width - radius),
            this.conversionRatio(y + height),
            this.conversionRatio(radius)
        );
        this.canvasContext.lineTo(
            this.conversionRatio(x + radius),
            this.conversionRatio(y + height)
        );
        this.canvasContext.arcTo(
            this.conversionRatio(x),
            this.conversionRatio(y + height),
            this.conversionRatio(x),
            this.conversionRatio(y + height - radius),
            this.conversionRatio(radius),
        );
        this.canvasContext.lineTo(
            this.conversionRatio(x),
            this.conversionRatio(y + radius),
        );
        this.canvasContext.arcTo(
            this.conversionRatio(x),
            this.conversionRatio(y),
            this.conversionRatio(x + radius),
            this.conversionRatio(y),
            this.conversionRatio(radius),
        );
        this.canvasContext.closePath()
    }
    /**
     * 绘制圆角矩形
     * @param {*} fillStyle 填充颜色
     * @param {*} x 绘制起始x坐标
     * @param {*} y 绘制起始y坐标
     * @param {*} width 矩形的宽
     * @param {*} height 矩形的高
     * @param {*} radius 圆角
     */
    drawRoundedRect(fillStyle, x, y, width, height, radius = 10) {
        if (radius <= 0) {
            console.warn("圆角大小不能为0")
            return
        }
        this.resetDefault()
        this.canvasContext.fillStyle = fillStyle;
        this.drawRoundedRectPath(x, y, width, height, radius)
        this.canvasContext.fill();
    }
    /**
     * 圆角矩形框
     * @param {*} strokeStyle 边框颜色
     * @param {*} x 绘制起始x坐标
     * @param {*} y 绘制起始y坐标
     * @param {*} width 矩形的宽
     * @param {*} height 矩形的高
     * @param {*} radius 圆角
     * @returns 
     */
    drawRoundedRectBorder(strokeStyle, x, y, width, height, radius = 10) {
        if (radius <= 0) {
            console.warn("圆角大小不能为0")
            return
        }
        this.resetDefault()
        this.canvasContext.strokeStyle = strokeStyle;
        this.drawRoundedRectPath(x, y, width, height, radius)
        this.canvasContext.stroke();
    }
    /**
     * 清除矩形区域
     * @param {*} x 清空起始x坐标
     * @param {*} y 清空起始y坐标
     * @param {*} width 清空区域的宽
     * @param {*} height 清空区域的高
     */
    clearRect(x, y, width, height) {
        this.canvasContext.clearRect(x, y, width, height)
    }
    /**
     * 绘制一个圆形路径
     * @param {*} x 圆心x坐标
     * @param {*} y 圆心y坐标
     * @param {*} r 半径
     */
    drawCirclePath(x, y, r) {
        that.canvasContext.beginPath();
        that.canvasContext.arc(that.conversionRatio(x), that.conversionRatio(y), that.conversionRatio(r), 0, 2 * Math.PI, false)
        that.canvasContext.closePath();
    }
    /**
     * 绘制圆
     * @param {*} x 圆心x坐标
     * @param {*} y 圆心x坐标
     * @param {*} r 半径
     */
    drawCircle(fillStyle, x, y, r) {
        this.resetDefault()
        this.canvasContext.fillStyle = fillStyle
        this.drawCirclePath(x, y, r)
        this.canvasContext.fill()
    }
    /**
     * 绘制圆边框
     * @param {*} strokeStyle 边框颜色
     * @param {*} x 圆心x坐标
     * @param {*} y 圆心x坐标
     * @param {*} r 半径
     */
    drawCircleBorder(strokeStyle, x, y, r) {
        this.resetDefault()
        this.canvasContext.strokeStyle = strokeStyle
        this.drawCirclePath(x, y, r)
        this.canvasContext.stroke()
    }
    /**
     * 绘制椭圆路径
     * @param {*} x 椭圆心x坐标
     * @param {*} y 椭圆心x坐标
     * @param {*} radiusX x轴半径
     * @param {*} radiusY y轴半径
     * @param {*} rotation 旋转角度
     */
    drawEllipsePath(x, y, radiusX, radiusY, rotation) {
        this.canvasContext.beginPath();
        this.canvasContext.ellipse(x, y, radiusX, radiusY, rotation, 0, Math.PI * 2);
        this.canvasContext.closePath()
    }
    /**
     * 绘制椭圆
     * @param {*} fillStyle 填充颜色
     * @param {*} x 椭圆心x坐标
     * @param {*} y 椭圆心x坐标
     * @param {*} radiusX x轴半径
     * @param {*} radiusY y轴半径
     * @param {*} rotation 旋转角度
     */
    drawEllipse(fillStyle, x, y, radiusX, radiusY, rotation) {
        this.resetDefault()
        this.canvasContext.fillStyle = fillStyle
        this.drawEllipsePath(x, y, radiusX, radiusY, rotation)
        this.canvasContext.fill()
    }
    /**
     * 绘制椭圆边框
     * @param {*} strokeStyle 边框颜色
     * @param {*} x 椭圆心x坐标
     * @param {*} y 椭圆心x坐标
     * @param {*} radiusX x轴半径
     * @param {*} radiusY y轴半径
     * @param {*} rotation 旋转角度
     */
    drawEllipseBorder(strokeStyle, x, y, radiusX, radiusY, rotation) {
        this.resetDefault()
        this.canvasContext.strokeStyle = strokeStyle
        this.drawEllipsePath(x, y, radiusX, radiusY, rotation)
        this.canvasContext.stroke()
    }
    /**
     * 绘制文字
     * @param {*} context 文字内容
     * @param {*} fontSize 字体大小
     * @param {*} color 文字颜色
     * @param {*} x 绘制起始X坐标
     * @param {*} y 绘制起始y坐标
     * @param {*} family 字体 如果加载了字体默认使用加载的字体
     * @param {*} textAlign 文字与x,y 的对其方式
     */
    drewText(context, fontSize, color, x, y, family, textAlign = 'left') {
        this.resetDefault()
        // 设置字体样式
        this.canvasContext.font = this.conversionRatio(fontSize) + 'px ' + family || this.family;
        // 填充文本
        this.canvasContext.fillStyle = color; // 设置填充颜色
        this.canvasContext.textAlign = textAlign
        this.canvasContext.fillText(context, this.conversionRatio(x), this.conversionRatio(y));

    }
    /**
     * 从中间绘制文字
     * @param {*} context 文字内容
     * @param {*} fontSize 字体大小
     * @param {*} color 文字颜色
     * @param {*} x 绘制起始X坐标
     * @param {*} y 绘制起始y坐标
     * @param {*} family 字体 如果加载了字体默认使用加载的字体
     */
    drewTextCenter(context, fontSize, color, x, y, family) {
        this.drewText(context, fontSize, color, x, y, family, "center")
    }
    /**
     * 从左侧绘制文字
     * @param {*} context 文字内容
     * @param {*} fontSize 字体大小
     * @param {*} color 文字颜色
     * @param {*} x 绘制起始X坐标
     * @param {*} y 绘制起始y坐标
     * @param {*} family 字体 如果加载了字体默认使用加载的字体
     */
    drewTextLeft(context, fontSize, color, x, y, family) {
        this.drewText(context, fontSize, color, x, y, family, "left")
    }
    /**
     * 从右侧绘制文字
     * @param {*} context 文字内容
     * @param {*} fontSize 字体大小
     * @param {*} color 文字颜色
     * @param {*} x 绘制起始X坐标
     * @param {*} y 绘制起始y坐标
     * @param {*} family 字体 如果加载了字体默认使用加载的字体
     */
    drewTextRight(context, fontSize, color, x, y, family) {
        this.drewText(context, fontSize, color, x, y, family, "right")
    }
    /**
     * 绘制图片(显示形式为cover)
     * @param {*} src 图片地址(建议同源)
     * @param {*} x 绘制起始X坐标
     * @param {*} y 绘制起始y坐标
     * @param {*} width 绘制区域的宽
     * @param {*} height 绘制区域的高
     * @param {*} radius 圆角 width=height且radius=width/2 是正圆
     */
    drewImage(src, x, y, width, height, radius) {
        let that = this
        return new Promise((resolve, reject) => {
            var img = new Image();   // 创建img元素
            img.src = src; // 设置图片源地址
            img.onload = function () {
                var imageW = this.naturalWidth;
                var imageH = this.naturalHeight;
                let imageX = 0
                let imageY = 0

                let imageRatio = Number((imageW / imageH).toFixed(4))
                let boxRatio = Number((width / height).toFixed(4))
                if (imageRatio > boxRatio) { //需要左右裁剪
                    imageX = Math.round((imageW - (imageH * boxRatio)) / 2)
                    imageW = boxRatio * imageH
                } else { //需要上下裁剪
                    imageY = Math.round((imageH - (imageW / boxRatio)) / 2)
                    imageH = Math.round(imageW / boxRatio)
                }
                if (radius && radius > 0) { //绘制圆角
                    if (width === height && Math.abs(radius - Math.round(height / 2)) <= 1) {
                        // 正圆
                        that.canvasContext.drawCirclePath(x + width / 2, y + height / 2, radius);
                    } else {
                        // 圆角矩形
                        that.drawRoundedRectPath(x, y, width, height, radius)
                    }
                    that.canvasContext.save()
                    // 剪切当前绘图区域
                    that.canvasContext.clip();
                }
                that.canvasContext.drawImage(
                    img,
                    imageX,
                    imageY,
                    imageW,
                    imageH,
                    that.conversionRatio(x),
                    that.conversionRatio(y),
                    that.conversionRatio(width),
                    that.conversionRatio(height),
                )
                if (radius && radius > 0) {
                    that.canvasContext.restore()
                }
                resolve()
            }
        })

    }
    /**
     * Blob下载
     * @param {*} blob new Bolb()数据
     * @param {*} fileName 文件名
     * @returns 
     */
    downloadBlobUrl(blob, fileName = "未命名") {
        if (!blob) return;
        if ("download" in document.createElement("a")) {
            // 非IE下载
            let link = document.createElement("a");
            if (window.URL) {
                link.href = window.URL.createObjectURL(blob);
            } else {
                link.href = window.webkitURL.createObjectURL(blob);
            }
            link.download = fileName;
            document.body.appendChild(link);
            link.click();
            link.remove();
        } else {
            // IE10+下载
            navigator.msSaveBlob(blob, fileName);
        }
    }
    /**
     * 下载canvas
     * @param {*} mimeType 文件类型 默认image/png
     * @param {*} qualityArgument 图片质量
     */
    async downloadCanvas(fileName, mimeType = "image/png", qualityArgument = 0.7) {
        let blob = await this.canvasToBlob(mimeType, qualityArgument)
        this.downloadBlobUrl(blob, fileName)
    }
    /**
     * canvas 获取base64
     * @param {*} mimeType 文件类型 默认image/png
     * @param {*} qualityArgument 图片质量
     * @returns 
     */
    canvasToBase64(mimeType = "image/png", qualityArgument = 0.7) {
        return this.canvasDom.toDataURL(mimeType, qualityArgument)
    }
    /**
     * canvas 获取Blob
     * @param {*} mimeType 文件类型 默认image/png
     * @param {*} qualityArgument 图片质量
     * @returns 
     */
    canvasToBlob(mimeType = "image/png", qualityArgument = 0.7) {
        return new Promise((resolve, reject) => {
            this.canvasDom.toBlob(
                (e) => {
                    resolve(e)
                },
                mimeType,
                qualityArgument
            )
        })
    }

} 